#include "gkt_list.h"
#include "gkt_board.h"
#include "gkt_debug.h"
#include "gkt_os.h"
#include "gkt_malloc.h"
#include "gkt_window.h"

/* window timer unit, in ms */
#ifdef GKT_CONFIG_WINDOW_TIMER_UNIT
#define WINDOW_TIMER_UNIT	GKT_CONFIG_WINDOW_TIMER_UNIT
#else
#define WINDOW_TIMER_UNIT	100
#endif

#define WINDOW_TIMER_THREAD_SAFE	0

typedef struct _window_timer {
	gkt_list_s	link;
	gkt_window_id_t	window_id;
	gkt_window_timer_t	id;
	uint32_t	flags;
	uint32_t	private_data;
	uint32_t	reload_ticks;	/* if  */
	uint32_t	expire_ticks;
} window_timer_s;

struct _window_timer_manager {
	gkt_mutex_t	mutex;
	gkt_soft_timer_t	soft_timer;
	gkt_list_s	active_timers_head;
	gkt_list_s	free_timers_head;
	uint32_t	ticks;

	gkt_window_timer_manage_handler	handlers[GKT_WINDOW_TIMER_MANAGE_MAX];
};
static struct _window_timer_manager s_window_timer_manager;

#define WINDOW_TIMER_MUTEX_LOCK()	\
	gkt_mutex_lock(s_window_timer_manager.mutex, GKT_INFINITE)
#define WINDOW_TIMER_MUTEX_UNLOCK()	\
	gkt_mutex_unlock(s_window_timer_manager.mutex)

static window_timer_s *window_timer_alloc(void)
{
	gkt_list_s *pos;

	if (gkt_list_is_not_empty(s_window_timer_manager.free_timers_head)) {
		pos = gkt_list_first(s_window_timer_manager.free_timers_head);
		gkt_list_delete_safe(pos);
		return __CONTAINER_OF(pos, window_timer_s, link);
	}

	return (window_timer_s *)gkt_malloc(sizeof(window_timer_s));
}

static void window_timer_free(window_timer_s *timer)
{
	gkt_list_add_tail(&s_window_timer_manager.free_timers_head, &timer->link);
}

static void window_timer_insert(window_timer_s *timer)
{
	window_timer_s *exist_timer;
	gkt_list_s *pos;

	gkt_list_foreach(&s_window_timer_manager.active_timers_head, pos) {
		exist_timer = __CONTAINER_OF(pos, window_timer_s, link);
		if (timer->expire_ticks < exist_timer->expire_ticks) {
			gkt_list_add_tail(&exist_timer->link, &timer->link);
			return;
		}
	}

	gkt_list_add_tail(&s_window_timer_manager.active_timers_head, 
				&timer->link);
}

static gkt_window_id_t window_timer_get_window_id(gkt_window_timer_t timer_id)
{
	gkt_window_s *window;
	uint32_t group;
	gkt_window_id_t window_id;

	group = (uint32_t)GKT_WINDOW_TIMER_GROUP(timer_id);
	if (GKT_WINDOW_TIMER_CUSTOM == group) {
		window = gkt_window_find(GKT_WINDOW_ID_CURRENT);
		GKT_ASSERT(window);
		window_id = window->desc->id;
	}
	else if (GKT_WINDOW_TIMER_WIDGET == group) {
		window = gkt_window_find(GKT_WINDOW_ID_ACTIVE);
		GKT_ASSERT(window);
		window_id = window->desc->id;
	}
	else
		window_id = -1;

	return window_id;
}

static window_timer_s *window_timer_find(gkt_window_timer_t timer_id)
{
	window_timer_s *timer;
	gkt_list_s *pos;
	gkt_window_id_t window_id;

	window_id = window_timer_get_window_id(timer_id);
	gkt_list_foreach(&s_window_timer_manager.active_timers_head, pos) {
		timer = __CONTAINER_OF(pos, window_timer_s, link);
		if ((timer_id == timer->id) && (window_id == timer->window_id))
			return timer;
	}

	return NULL;
}

#ifdef GKT_CONFIG_WINDOW_TIMER_EVENT_PUT_TIMEOUT
#define WINDOW_TIMER_EVENT_PUT_TIMEOUT	GKT_CONFIG_WINDOW_TIMER_EVENT_PUT_TIMEOUT
#else
#define WINDOW_TIMER_EVENT_PUT_TIMEOUT	(WINDOW_TIMER_UNIT - 10)
#endif

static void window_timer_callback(void *params)
{
	window_timer_s *timer;
	gkt_event_s event;
	gkt_list_s *pos;
	int retval;

	WINDOW_TIMER_MUTEX_LOCK();

	s_window_timer_manager.ticks++;

	gkt_list_foreach(&s_window_timer_manager.active_timers_head, pos) {
		timer = __CONTAINER_OF(pos, window_timer_s, link);
		if (s_window_timer_manager.ticks < timer->expire_ticks)
			break;
		else if (timer->expire_ticks == s_window_timer_manager.ticks) {
			/* delete item from list */
			gkt_list_delete_safe(&timer->link);

			/* push timer expire event */
			event.window_id = timer->window_id;
			event.flags = 0;
			event.major_type = GKT_EVENT_TIMER;
			event.minor_type = timer->id;
			event.data[GKT_WINDOW_TIMER_EVTDATA_PRIVATE_DATA] = timer->private_data;
			retval = gkt_event_put(&event, WINDOW_TIMER_EVENT_PUT_TIMEOUT);
			if (retval != GKT_SUCCESS) {
				gkt_error("window_timer: (%d) window_id(%d), timer_id(%d)\n",
							retval, timer->window_id, timer->id);
			}

			if (timer->reload_ticks) {
				timer->expire_ticks += timer->reload_ticks;
				window_timer_insert(timer);
			}
			else
				window_timer_free(timer);
		}
	}

	WINDOW_TIMER_MUTEX_UNLOCK();
}

int gkt_window_timer_set(gkt_window_timer_t timer_id, 
				uint32_t interval, uint32_t flags,
				uint32_t private_data)
{
	window_timer_s *timer;
	uint32_t ticks;
	int retval;

	if (interval) {
		ticks = (interval + WINDOW_TIMER_UNIT - 1) / WINDOW_TIMER_UNIT;
	
		WINDOW_TIMER_MUTEX_LOCK();

		timer = window_timer_find(timer_id);
		if (timer)
			gkt_list_delete_safe(&timer->link);
		else {
			timer = window_timer_alloc();
			if (!timer) {
				WINDOW_TIMER_MUTEX_UNLOCK();
				return GKT_ENOMEMORY;
			}

			timer->window_id = window_timer_get_window_id(timer_id);
			timer->id = timer_id;
		}

		timer->flags = flags;
		timer->private_data = private_data;
		timer->reload_ticks = (flags & GKT_WINDOW_TIMER_F_PERIODIC) ? ticks : 0;
		timer->expire_ticks = s_window_timer_manager.ticks + ticks;
		window_timer_insert(timer);

		WINDOW_TIMER_MUTEX_UNLOCK();

		retval = GKT_SUCCESS;
	}
	else
		retval = GKT_EPARAM;

	return retval;
}

int gkt_window_timer_clear(gkt_window_timer_t timer_id)
{
	window_timer_s *timer;
	int retval;

	WINDOW_TIMER_MUTEX_LOCK();
	timer = window_timer_find(timer_id);
	if (timer) {
		gkt_list_delete_safe(&timer->link);
		window_timer_free(timer);
		retval = GKT_SUCCESS;
	}
	else
		retval = GKT_ENOTEXIST;
	WINDOW_TIMER_MUTEX_UNLOCK();

	return retval;
}

void gkt_window_timer_clear_all(gkt_window_id_t window_id)
{
	window_timer_s *timer;
	gkt_list_s *pos;

	WINDOW_TIMER_MUTEX_LOCK();
	gkt_list_foreach(&s_window_timer_manager.active_timers_head, pos) {
		timer = __CONTAINER_OF(pos, window_timer_s, link);
		if ((timer->window_id == window_id)
			&& !(timer->flags & GKT_WINDOW_TIMER_F_DETACHED)) 
		{
			gkt_list_delete_safe(&timer->link);
			window_timer_free(timer);
		}
	}
	WINDOW_TIMER_MUTEX_UNLOCK();
}

static int gkt_window_timer_event_handler(gkt_event_s *event)
{
	gkt_window_s *window;
	uint32_t group;

	group = event->minor_type & GKT_WINDOW_TIMER_GROUP_MASK;
	if (GKT_WINDOW_TIMER_MANAGE == group) {
		uint32_t sub_group;

		sub_group = (event->minor_type & GKT_WINDOW_TIMER_SUB_GROUP_MASK) 
						>> GKT_WINDOW_TIMER_SUB_GROUP_SHIFT;
		if ((sub_group < GKT_WINDOW_TIMER_MANAGE_MAX)
			&& s_window_timer_manager.handlers[sub_group])
		{
			uint32_t type = event->minor_type & GKT_WINDOW_TIMER_MANAGE_TYPE_MASK;
			uint32_t private_data = event->data[GKT_WINDOW_TIMER_EVTDATA_PRIVATE_DATA];
			(*s_window_timer_manager.handlers[sub_group])(type, private_data);
		}
		else
			gkt_info("window_timer: MANAGER - id(%u)\n", event->minor_type);
	}
	else if (GKT_WINDOW_TIMER_CUSTOM == group) {
		window = gkt_window_find((gkt_window_id_t)event->window_id);
		if (__LIKELY(window)) {
			gkt_window_timer_t timer_id = event->minor_type & (~GKT_WINDOW_TIMER_GROUP_MASK);
			gkt_window_custom_event_handle(window, 
					GKT_EVENT_TIMER, (uint32_t)timer_id, 
					0, event->data[GKT_WINDOW_TIMER_EVTDATA_PRIVATE_DATA]);
		}
	}
	else if (GKT_WINDOW_TIMER_WIDGET == group) {
		window = gkt_window_find((gkt_window_id_t)event->window_id);
		if (__LIKELY(window)) {
			gkt_widget_id_t widget_id = event->minor_type & (~GKT_WINDOW_TIMER_GROUP_MASK);
			if (__LIKELY((uint16_t)widget_id < window->desc->widget_nums)) {
				gkt_widget_s *widget = window->widgets + widget_id;
				gkt_widget_event_dispatch(widget, GKT_EVENT_TIMER, 
						0, event->data[GKT_WINDOW_TIMER_EVTDATA_PRIVATE_DATA]);
			}
		}
	}
	else
		gkt_error("window_timer: id(0x%x) - GROUP UNKNOWN!\n", event->minor_type);

	return GKT_EVENT_HANDLE_COMPLETE;
}

void gkt_window_timer_register_manage_handler(uint32_t sub_group,
			gkt_window_timer_manage_handler handler)
{
	if (sub_group < GKT_WINDOW_TIMER_MANAGE_MAX)
		s_window_timer_manager.handlers[sub_group] = handler;
}

int gkt_window_timer_init(void)
{
	s_window_timer_manager.mutex = gkt_mutex_create(GKT_MUTEX_RECURSIVE);
	if (!s_window_timer_manager.mutex) {
		gkt_error("window_timer: create mutex failed.\n");
		return GKT_ENORESOURCE;
	}

	gkt_list_init(&s_window_timer_manager.active_timers_head);
	gkt_list_init(&s_window_timer_manager.free_timers_head);
	s_window_timer_manager.ticks = 0;

	s_window_timer_manager.soft_timer = gkt_soft_timer_create(
					GKT_SOFT_TIMER_PERIODIC, 
					window_timer_callback, NULL);
	if (!s_window_timer_manager.soft_timer) {
		gkt_error("window_timer: create soft timer failed.\n");
		return GKT_ENORESOURCE;
	}

	return gkt_soft_timer_start(s_window_timer_manager.soft_timer, 
						WINDOW_TIMER_UNIT);
}

#define gkt_window_timer_mask	NULL

GKT_EVENT_DESC_DEFINE(TIMER, window_timer);

